package com.seer.srd.qingdao

import com.seer.srd.BusinessError
import com.seer.srd.db.MongoDBManager
import com.seer.srd.handler.CfgHandler.logger
import com.seer.srd.robottask.RobotTask
import com.seer.srd.robottask.RobotTaskService
import com.seer.srd.robottask.buildTaskInstanceByDef
import com.seer.srd.robottask.component.getHelper
import com.seer.srd.robottask.getRobotTaskDef
import com.seer.srd.scheduler.ThreadFactoryHelper
import com.seer.srd.storesite.StoreSite
import com.seer.srd.storesite.StoreSiteService
import org.litote.kmongo.findOne
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

object TaskListener {
    // 测试使用,置为 true 防止大批量生成无效任务。
    private var lock = false

    // 从配置文件读取信息
    private val config = CUSTOM_CONFIG

    // 与plc建立连接
    private val plc = CUSTOM_CONFIG.plc.values.first()
    private val plcHelper = getHelper(plc.host, plc.port)

    // 维护立库叫料状态
    private var plcCall = HashMap<String, Boolean>()

    //维护暂存区的参数信息，不同巷道可以设置不同的参数，保证互不干扰。
    val tempAreaParam = HashMap<String, Int?>()

    //维护满足条件的巷道及其可加入运输任务的库位。 todo 关闭系统之后，信息丢失。需要持久化，或者改变实现方式：可以生成任务，等待选择一个终点，才能继续执行。
    private val boxUploadGalleryMap = LinkedHashMap<String, LinkedHashSet<String>>()

    //维护缓存区的参数信息,该参数为安全间距。
    var bufferAreaParam: Int? = -1

    //开启线程
    private val executorForCreatTask =
        Executors.newSingleThreadScheduledExecutor(ThreadFactoryHelper.buildNamedThreadFactory("srd-qingdaoFAW"))
    private val executorForCheckingCondition =
        Executors.newSingleThreadScheduledExecutor(ThreadFactoryHelper.buildNamedThreadFactory("srd-qingdaoFAW"))

    fun init() {
        logger.debug("init qingdaoFAW project!")
        try {
            config.tempAreaParam.forEach { (name, param) ->
                tempAreaParam[name] = param
            }
            bufferAreaParam = CUSTOM_CONFIG.bufferAreaParam
            //与plc建立长连接
            plcHelper.connect()
            executorForCheckingCondition.scheduleAtFixedRate(this::checkConditionOfTempArea, 1, 3, TimeUnit.SECONDS)
            executorForCreatTask.scheduleAtFixedRate(this::createTask, 1, 3, TimeUnit.SECONDS)
        }catch (e:Exception){
            logger.error("init qingdaoFAW project failed")
        }
        logger.debug("init qingdaoFAW project success!")
    }

    /**
     * 查询暂存区状态，将符合要求的巷道以及库位加入任务待办列表
     * 需要考虑的参数有：[num = tempAreaParam"巷道名"] -从该巷道第一个库位开始，连续有 num个位置有货则将 巷道："[连续 num 个库位]" 加入[ 可执行任务巷道 boxUploadGalleryMap ]，
     * 执行任务时，从集合中顺序选取一个库位,作为transferA的起点，并将其从集合中移除，若集合为空则将该巷道从[boxUploadGalleryMap]中移除。
     */
    private fun checkConditionOfTempArea() {
        logger.debug("start to check temp area")
        try {
            val galleryListOfTemp = CUSTOM_CONFIG.galleryTempArea
            //遍历暂存区巷道，找到符合条件的巷道，并将该巷道取货位置加入 boxUploadGalleryMap
            galleryListOfTemp.forEach { (name, sites) ->
                //取到该巷道的参数
                val num = tempAreaParam[name]//检查每个巷道的运输条件
                var count = 0
                if (boxUploadGalleryMap.contains(name)) {
                    if (boxUploadGalleryMap[name]!!.size == 0) {
                        boxUploadGalleryMap.remove(name) // 巷道内的箱子运完，即可移除该巷道
                        return@forEach
                    } else return@forEach  // 巷道内箱子未运完，不做处理
                } //巷道若存在于集合则不再加入

                // boxUploadGalleryMap 中不存在该巷道，需要检查此轮是否满足条件。
                sites.forEach loop@{ id ->
                    val site = StoreSiteService.getStoreSiteById(id)
                    if (site != null) {
                        if (site.filled) count++ else return@loop
                    }
                }
                if (count == num) { //如果连续 num 个位置有货则判为满足条件，将该巷道下num个库位加入加入transferA起点选择集合
                    val allowSitesSet = LinkedHashSet<String>()
                    val sitesOfGallery = CUSTOM_CONFIG.galleryTempArea[name]!!

                    for (i in 0 until count) {
                        allowSitesSet.add(sitesOfGallery[i])
                    }
                    boxUploadGalleryMap[name] = allowSitesSet
                }
            }
        } catch (e: Exception) {
            logger.error("exist error in checkConditionOfTempArea ", e)
        }
        logger.debug("check temp area end")
    }

    /**
     * 创建任务
     * transferA(暂存区 -> 缓存区运输) 任务选取[ 可执行运输任务巷道的库位 ]作为起点，选取[ 类型匹配的缓存区巷道中满足安全间距的空库位 ]作为终点。
     * transferB(缓存区 -> 立库运输)  任务选择[ 立库缺料库口 ]为终点，[ 对应箱型满足安全间距的满库位 ]作为起点。其中立库的缺料状况由SRD查询PLC获知。
     */
    private fun createTask() {
        logger.debug("start create tasks")
        queryPlcCall()  // check PLC calling info and maintain condition to plcCall
        creatTransferToBufferTask() // create transferA
        createTransferToStoreTask() // create transferB
        logger.debug("create tasks success!")
    }

    /**
     * 查询PLC信息,维护状态到[plcCall]：监听到入库口 [托盘到、无货物] 的状态即可判定为[ 呼叫货物入库 ]。
     */
    private fun queryPlcCall() {
        logger.debug("query plc condition start")
        try {
            val address = config.plc["store"]?.address!!
            val bigTrayOn = plcHelper.read03HoldingRegisters(address.bigTrayOn, 1, 1, "读取大箱托盘到位")?.getShort(0)?.toInt()
            val smallTrayOn =
                plcHelper.read03HoldingRegisters(address.smallTrayOn, 1, 1, "读取小箱托盘到位")?.getShort(0)?.toInt()
            val bigFull = plcHelper.read03HoldingRegisters(address.bigFull, 1, 1, "读取大箱托盘空满")?.getShort(0)?.toInt()
            val smallFull = plcHelper.read03HoldingRegisters(address.smallFull, 1, 1, "读取小箱托盘空满")?.getShort(0)?.toInt()

            //托盘到位且为空，判定为叫料
            val bigCall: Boolean = bigTrayOn == 1 && bigFull == 0

            if (plcCall["big"] == null || (plcCall["big"] as Boolean) != bigCall) plcCall["big"] = bigCall

            val smallCall: Boolean = smallTrayOn == 1 && smallFull == 0
            if (plcCall["small"] == null || (plcCall["small"] as Boolean) != smallCall) plcCall["small"] = smallCall
        } catch (e: Exception) {
            logger.error("exist error in query plc", e)
        }
        logger.debug("query plc condition end")
    }

    /**
     * 创建transferA
     */
    //创建暂存区->缓存区任务
    private fun creatTransferToBufferTask() {
        logger.debug("start to creat task of tempArea to bufferArea!")
        try {
            if (boxUploadGalleryMap.isNotEmpty()) {
                val galleryList = boxUploadGalleryMap.keys.toList() //拿到满足运输条件的巷道列表
                if (galleryList.isNotEmpty()) {//存在满足条件的巷道
                    var toBuffer = ""
                    var fromTempSite = ""
                    for (i in galleryList.indices) {
                        val galleryTemp = galleryList[i]
                        val split = galleryTemp.split("-")
                        val galleryBuffer = split[0].plus("-${split[1]}").replace("WT", "WB") //这里严格依赖于库位命名
                        //找对应巷道可以放货的空库位
                        toBuffer = selectSafeDistanceEmptySite(galleryBuffer)
                        if (toBuffer.isBlank()) return
                        //找到巷道剩余的第一个库位取货，并将该库位从对应库位集合中删去
                        if (boxUploadGalleryMap.size <= 0 || boxUploadGalleryMap[galleryTemp] == null || boxUploadGalleryMap[galleryTemp]!!.size <= 0) break

                        fromTempSite = boxUploadGalleryMap[galleryTemp]!!.first()
                        generateTask("transferA", fromTempSite, toBuffer)

                        boxUploadGalleryMap[galleryTemp]!!.remove(fromTempSite)//删除已经加入任务的库位
                        logger.debug("remove $fromTempSite from the sites set of $galleryTemp")
                    }
                }
            }
        } catch (e: Exception) {
            logger.error("exist error in creatTransferToBufferTask ", e)
        }
        logger.debug("creatTransferToBufferTask success!")
    }

    //实时选择暂存区->缓存区的任务终点(满足安全间距)
    private fun selectSafeDistanceEmptySite(galleryTypePrefix: String): String {
        var position = -1 //记录巷道上锁位置index，为-1 则不存在上锁。
        val safeDistance = config.bufferAreaParam
        //取到对应缓存去对应类型的巷道
        val galleryList = config.galleryBufferArea.keys.filter { it.contains(galleryTypePrefix) }
        galleryList.forEach {
            val sitesArray = config.galleryBufferArea[it]!!
            for (i in sitesArray.size - 1 downTo 0) {
                val site = StoreSiteService.getStoreSiteById(sitesArray[i])!!
                //倒序找到第一个上锁的位置（适应库位分布采用倒序）
                if (site.locked) {
                    position = i
                    break
                }
            }
            if (position == -1) {//若不存在上锁位置，第一个位置满，直接退出，否则找第一个有货物的前一个位置放置
                var emptyPosition = sitesArray.size
                for (k in sitesArray.size - 1 downTo 0) {
                    val site = StoreSiteService.getStoreSiteById(sitesArray[k])!!
                    if (site.filled) break else emptyPosition--
                }
                if (emptyPosition != sitesArray.size) {
                    logger.debug("choose ${sitesArray[emptyPosition]} to put box")
                    return sitesArray[emptyPosition]
                }
            } else { //存在上锁位置
                //若该库位为空状态，则代表transferA正在处理该库位，此时直接退出
                val siteLocked = StoreSiteService.getStoreSiteById(sitesArray[position])!!
                if (!siteLocked.filled) {
                    logger.debug("this empty site has been locked by another task of transferA!")
                    return ""
                }
                //若该库位为满状态，则代表transferB正在处理该库位，此时寻找安全间距之外的第一个空库位
                if (siteLocked.filled) {
                    if (position + safeDistance > sitesArray.size) {
                        logger.debug("no safe site to put box")
                        return ""
                    } else {
                        for (j in position + safeDistance until sitesArray.size) {
                            val siteEmpty = StoreSiteService.getStoreSiteById(sitesArray[j])!!
                            if (!siteEmpty.filled) {
                                logger.debug("choose ${sitesArray[j]} to put box")
                                return sitesArray[j]
                            }
                        }
                    }
                }
            }
        }
        logger.debug("not find site to put box!")
        return ""
    }

    /**
     * 创建transferB
     */
    //创建缓存区->立库任务
    private fun createTransferToStoreTask() {
        try {
            logger.debug("start to creat task of bufferArea to Store!")
            val stores = plcCall.filter { it.value }
            if (stores.isEmpty()) return           //立库无叫料
            stores.forEach { (type, _) ->
                var fromBuffer = ""
                var toStore = ""
                if (type == "big") {
                    toStore = config.store["bigBox"] as String
                    fromBuffer = selectSafeDistanceFullSite("big")
                } else {
                    toStore = config.store["smallBox"] as String
                    fromBuffer = selectSafeDistanceFullSite("small")
                }
                //过滤重复请求
                val toStoreTaskList =
                    MongoDBManager.collection<RobotTask>().find().filter { it.persistedVariables["to"] == toStore }
                        .filter { it.state == 0 }
                if (toStoreTaskList.isEmpty()) generateTask("transferB", fromBuffer, toStore)
            }
            logger.debug("creat task of bufferArea to Store success!")
        } catch (e: Exception) {
            logger.error("exist in createTransferToStoreTask", e)
        }
    }

    //实时选择缓存区->立库的任务终点
    private fun selectSafeDistanceFullSite(boxType: String): String {
        val safeDistance = bufferAreaParam!! //安全间距
        if (boxType == "big") { //big box
            //拿到大箱型的巷道
            //顺序检查第一个有货、未上锁、满足安全间距的库位
            val bigGallerySitesArray = config.galleryBufferArea["WB-TW103-1-1"]!!
            return traverseToFindTargetSite(safeDistance, bigGallerySitesArray)
        } else { // small box
            config.galleryBufferArea.forEach { (name, array) ->
                if (name.contains("103")) return@forEach
                val site = traverseToFindTargetSite(safeDistance, array)
                if (site.isNotBlank()) return site
            }
        }
        return ""
    }

    //遍历缓存区巷道找到一个满足条件的满库位
    private fun traverseToFindTargetSite(safeDistance: Int, array: Array<String>): String {
        var site: StoreSite?
        for (i in array.indices) {
            site = StoreSiteService.getStoreSiteById(array[i])
            if (site == null) return ""
            if (site.filled) {  //找满库位取箱子
                if (site.locked) break  //已经被另一个任务锁定，则该巷道此轮不能再考虑
                else {
                    var nextLocked = -1
                    for (k in i until array.size) {
                        if (StoreSiteService.getStoreSiteById(array[k])!!.locked) nextLocked = k
                    }
                    if (nextLocked == -1 || nextLocked - i >= safeDistance) return array[i]
                }
            }
        }
        logger.debug("there is no full site to take box in this buffer gallery!")
        return ""
    }

    //生成起点到终点的任务
    private fun generateTask(taskDef: String, from: String, to: String) {
        if (lock) return  //测试时关闭任务创建
        logger.debug("start generate task")
        if (from.isBlank() || to.isBlank()) return
        try {
            val def = getRobotTaskDef(taskDef) ?: throw BusinessError("There is no '$taskDef' in taskdef's list")
            val task = buildTaskInstanceByDef(def)
            task.persistedVariables["from"] = from
            task.persistedVariables["to"] = to
            if (taskDef == "transferA") {//将叉齿操作的预备高度存入任务持久化变量中
                logger.debug("get layers info from $from")
                task.persistedVariables["layerInfo"] = arrayListOf(1, 2, 3).toString()  // 用于测试，正式需要删除 todo

                // 查询起点库位的content 取到height layers 具体见 RoboViewManager.kt line 309
                // 取到配置文件中的 operateHeight 信息对数据进行运算得到叉车叉货时的预备高度,以list方式存入(处理方式按list处理的)。


//                val fromSite = StoreSiteService.getStoreSiteById(from) ?: throw BusinessError("$from 不存在，请检查地图以及配置文件")
//                val content = fromSite.content
//                if (content.isBlank()) {
//                    throw BusinessError("$from site's content is empty,siteInfo:$fromSite")
//                } else {
//                    val splits = content.split(";")
//                    val heightOfBox = splits[0].split("-")[2].toDouble()
//                    if (!splits[1].contains("layers")) throw BusinessError("not exit information of layers num")
//                    else {
//                        val layers: Int = splits[1].split("-")[1].replace(";", "").toInt()
//                        setOperationHeightInfoToBuffer(heightOfBox,layers,from,task)
//                    }
//                }

                logger.debug("setting layers info to $to success")
            }
            RobotTaskService.saveNewRobotTask(task)
        } catch (e: Exception) {
            throw BusinessError("exist error in generate task",e)
        }
        logger.debug("generate task success")
    }

    // 处理得到叉车每层操作的预备高度
    private fun setOperationHeightInfoToBuffer(heightOfBox: Double, layersNum: Int, siteName: String,task:RobotTask) {

        try {
            val splits = siteName.split("-")
            val galleryOfSite = splits[0].plus("-").plus(splits[1])

            val operationHeightInfo = CUSTOM_CONFIG.operateHeight[galleryOfSite]
            val firstLayer = operationHeightInfo!!.space.div(2) * 1
            val secondLayer = operationHeightInfo.space.div(2) * 3 + 1 * heightOfBox
            val thirdLayer = operationHeightInfo.space.div(2) * 5 + 2 * heightOfBox // 理论上的最高层
            when( layersNum){
                1 ->  task.persistedVariables["layerInfo"] = arrayListOf(firstLayer)
                2 -> task.persistedVariables["layerInfo"] = arrayListOf(firstLayer,secondLayer)
                3 -> task.persistedVariables["layerInfo"] =  arrayListOf(firstLayer,secondLayer,thirdLayer)
                else ->{
                    throw BusinessError("$layersNum is illegal")
                }
            }
        }catch (e:Exception){
            logger.error("set info:$heightOfBox,$$layersNum to buffer site failed",e)
        }

}}
